#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "common.h"
#include "env.h"
#include "link.h"

int link_init()
{
#ifdef _WIN32
	WSADATA wsaData;

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) {
		DPRINT("[l] WSAStartup failed");
		return LINK_ERROR;
	}
#endif

	return LINK_SUCCESS;
}

int link_connect(link_t* l)
{
	struct sockaddr_in addr;

	assert(l != NULL);
	if (l == NULL) {
		return LINK_ERROR;
	}

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = l->addr;
	addr.sin_port = htons(l->port);

	DPRINT("[l] connecting to %s:%d...", inet_ntoa(addr.sin_addr), l->port);

	l->sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (l->sockfd < 0) {
		DPRINT("[l] FAILED (error on socket())");
		return LINK_ERROR;
	}

	if (connect(l->sockfd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		DPRINT("[l] FAILED (error on connect(), errno=%s)", strerror(errno));
		return LINK_ERROR;
	}

	DPRINT("[l] OK");

	return LINK_SUCCESS;
}

int link_listen(link_t* l, int max_connections)
{
	struct sockaddr_in local_addr;
	socklen_t addr_len;
	int on = 1;

	assert(l != NULL);
	assert(max_connections > 0);

	if (l == NULL || max_connections < 1) {
		return LINK_ERROR;
	}

	DPRINT("[l] listening...");

	l->sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (l->sockfd < 0) {
		DPRINT("[l] FAILED (error on socket())");
		return LINK_ERROR;
	}

	if (setsockopt(l->sockfd, SOL_SOCKET, SO_REUSEADDR, (char*) &on, sizeof(on)) < 0) {
		DPRINT("[l] FAILED (error on setsockopt(), errno=%s)", strerror(errno));
		link_close(l);
		return LINK_ERROR;
	}

	memset(&local_addr, 0, sizeof(struct sockaddr_in));
	local_addr.sin_family = AF_INET;
	local_addr.sin_addr.s_addr = INADDR_ANY;
	local_addr.sin_port = htons(l->port);

	if (bind(l->sockfd, (struct sockaddr *) &local_addr, sizeof(struct sockaddr)) < 0) {
		link_close(l);
		DPRINT("[l] FAILED (error on bind())");
		return LINK_ERROR;
	}

	/* get the port assigned */
	addr_len = sizeof(struct sockaddr);
	if (getsockname(l->sockfd, (struct sockaddr *) &local_addr, &addr_len) < 0) {
		link_close(l);
		DPRINT("link error on getsockname()");
		return LINK_ERROR;
	}

	l->port = ntohs(local_addr.sin_port); /* set new port if '0' was used */

	DPRINT("[l] bound to port %d", l->port);

	if (listen(l->sockfd, max_connections) < 0) {
		link_close(l);
		DPRINT("[l] FAILED (error on listen())");
		return LINK_ERROR;
	}

	DPRINT("[l] OK");

	return LINK_SUCCESS;
}

int link_accept(link_t* l, on_accept fp_accept, void* arg)
{
	struct sockaddr_in remote_addr;
	socklen_t sockfd_len;
	int remote_sockfd;

	assert(l != NULL);
	assert(fp_accept != NULL);

	if (l == NULL || fp_accept == NULL) {
		return LINK_ERROR;
	}

	DPRINT("[l] accepting...");

	sockfd_len = sizeof(struct sockaddr_in);
	while ((remote_sockfd = accept(l->sockfd, (struct sockaddr *) &remote_addr, &sockfd_len)) != -1) {
		link_t remote;
		int abort = 0;

		remote.sockfd = remote_sockfd;
		remote.addr = remote_addr.sin_addr.s_addr;
		remote.port = remote_addr.sin_port;

		DPRINT("[l] accepted a new connection from %s:%d", inet_ntoa(remote_addr.sin_addr), remote.port);

		fp_accept(&remote, arg, &abort);

		if (abort) {
			break;
		}
	}

	link_close(l);

	if (remote_sockfd < 0) {
		DPRINT("[l] FAILED (error on accept())");
		return LINK_ERROR;
	}

	DPRINT("[l] OK");

	return LINK_SUCCESS;
}

int link_send_raw(link_t* l, char* buffer, int size)
{
	int sent = 0;

	assert(l != NULL);
	assert(buffer != NULL);

	if (l == NULL || buffer == NULL) {
		return LINK_ERROR;
	}

	while (sent < size) {
		int src = send(l->sockfd, buffer + sent, size - sent, 0);

		if (src < 0) {
			DPRINT("[l] failed to send (error on send())");
			return LINK_ERROR;
		}

		sent += src;
	}

	return LINK_SUCCESS;
}

int link_receive_raw(link_t* l, char* buffer, int size, int* received)
{
	int src;

	assert(l != NULL);
	assert(buffer != NULL);

	if (l == NULL || buffer == NULL) {
		return LINK_ERROR;
	}

	src = recv(l->sockfd, buffer, size, 0);
	if (src == 0) {
		DPRINT("[l] failed to receive (remote host disconnected)");
		return LINK_DISCONNECTED;
	}

	if (received != NULL){
		*received = src;
	}

	return LINK_SUCCESS;
}

int link_close(link_t* l)
{
	assert(l->sockfd >= 0);

	DPRINT("[l] closing link");

	if (l->sockfd >= 0) {
		close(l->sockfd);
		l->sockfd = -1;
	}

	return LINK_SUCCESS;
}
